package com.opener.common.encrypt;

import com.opener.common.utils.Radix64;
import com.opener.web.auth.model.UserAuthToken;
import org.apache.commons.codec.digest.DigestUtils;

import java.util.*;

import static java.lang.Character.MAX_RADIX;

/**
 *
 * @author baboy
 * @date 12/07/2017
 */
public class EncryptUtils {

    public final static String VER_AA = "AA";
    public final static String VER_AC = "AC";
    public final static String VER_BA = "BA";

    public final static String MAP_KEY_VER = "ver";
    public final static String MAP_KEY_STR = "s";
    public final static String MAP_KEY_ORI = "ori";
    public final static String MAP_KEY_SALT = "salt";
    public final static String MAP_KEY_CONTENT = "content";
    public final static String MAP_KEY_ENCRYPT_STR_LEN = "len";
    public final static String MAP_KEY_CHECK_CODE = "checkCode";
    public final static String MAP_KEY_TIME = "time";

    private static int DATA_FLAG_LEN = 4;
    private static int SALT_LEN = 8;
    private static int TIME_LEN = 10;
    private static int CHECK_CODE_LEN = 8;
    private static int SHORT_TIME_LEN = 7;
    private static int SHORT_DATA_FLAG_LEN = 4;

    public static Long convertStringToNumber(String s, int radix){
        Long n = 0L;
        if (radix == 64){
            n = Radix64.parseLong(s);
            return n;
        }
        n = Long.parseLong(s, Character.MAX_RADIX);
        return n;
    }

    private static String convertNumberToString(long n,int radix){
        if (radix == 64){
            return Radix64.toString(n);
        }
        return Long.toString(n, radix);
    }

    /**
     * 把数字n格式化，长度为len
     * @param n
     * @param len
     * @param radix
     * @return
     */
    public static String formatNumber(int n, int len, int radix){
        String tmp = "0000000000"+Integer.toString(n, Character.MAX_RADIX);
        String ret = tmp.substring(tmp.length() - len);
        return ret;
    }
    /**
     *
     * AC|LEN|SALT|STR|CHECKCODE
     * LEN=RADIO(36)
     * SALT = SHA(UUID)
     * PWD = SHA(SALT)
     * STR = AES.EN(c,PWD)
     * CHECKCODE = SHA(c)
     * 加密方式为：AB
     * @param c 加密💰字符串
     * @return 返回加密串
     */
    private static String encryptAA(String c){
        StringBuffer sb = new StringBuffer(VER_AA);

        //SALT:salt
        String salt = DigestUtils.sha1Hex(c).substring(0, SALT_LEN);
        String pwd = DigestUtils.sha1Hex(salt);

        //STR:encryptString
        String oriString = c;
        String encryptString = AesUtils.encryptString(oriString, pwd);

        //LEN:sLen
        int len = salt.length() + encryptString.length();
        String sLen = "0000"+Integer.toString(len, 36);
        sLen = sLen.substring(sLen.length() - SHORT_DATA_FLAG_LEN);

        // CHECKCODE:checkCode
        String checkCode = DigestUtils.sha1Hex( oriString ).substring(0, CHECK_CODE_LEN);

        sb.append(sLen);
        sb.append(salt);
        sb.append(encryptString);
        sb.append(checkCode);
        return sb.toString().toUpperCase();
    }
    private static Map parseAA(String str){
        try {
            String s = str.toLowerCase();
            Map ret = new HashMap();
            ret.put(MAP_KEY_STR,s);
            int i = 0;
            String ver = s.substring(i, VER_AA.length());
            if(ver == null || !VER_AA.equalsIgnoreCase(ver)){
                return null;
            }

            ret.put(MAP_KEY_VER, ver);

            i += VER_AA.length();
            String sLen = s.substring(i,i+ SHORT_DATA_FLAG_LEN);
            sLen = sLen.replaceFirst("^0+","");
            int nLen = Integer.parseInt(sLen,36);
            ret.put(MAP_KEY_ENCRYPT_STR_LEN, nLen);

            i += SHORT_DATA_FLAG_LEN;
            String salt = s.substring(i, i + SALT_LEN);
            if(salt == null){
                return null;
            }
            ret.put(MAP_KEY_SALT, salt);

            i += SALT_LEN;
            String encryptString = s.substring(i, i + nLen - SALT_LEN );
            if(encryptString == null){
                return null;
            }
            i += nLen - SALT_LEN;
            if((i+1) >= s.length()){
                return null;
            }
            String pwd = DigestUtils.sha1Hex(salt);
            String oriString = AesUtils.decryptString(encryptString, pwd);
            if(oriString == null ){
                return null;
            }
            ret.put(MAP_KEY_ORI, oriString);
            String checkCode = s.substring(i);
            ret.put(MAP_KEY_CHECK_CODE, checkCode);
            String checkCode2 = DigestUtils.sha1Hex(oriString).substring(0,checkCode.length());
            if( checkCode.equalsIgnoreCase( checkCode2)){
                String c = oriString;
                ret.put(MAP_KEY_CONTENT,c);
                return  ret;
            }
            return null;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    /**
     *
     * AC|LEN|SALT|STR|CHECKCODE
     * LEN=RADIO(36)
     * SALT = SHA(UUID)
     * PWD = SHA(SALT)
     * STR = AES.EN(t+c,PWD)
     * CHECKCODE = SHA(c)
     * 加密方式为：AB
     * @param c 加密💰字符串
     * @return 返回加密串
     */
    private static String encryptAC(String c){
        StringBuffer sb = new StringBuffer(VER_AC);
        //TIMESTR
        long t = System.currentTimeMillis()/1000;
        String timeStr = "000"+Long.toString(t, MAX_RADIX );
        timeStr = timeStr.substring(timeStr.length() - SHORT_TIME_LEN);

        //SALT:salt
        String salt = DigestUtils.sha1Hex(UUID.randomUUID().toString()).substring(0, SALT_LEN);
        String pwd = DigestUtils.sha1Hex(salt);

        //STR:encryptString
        String oriString = timeStr+c;
        String encryptString = AesUtils.encryptString(oriString, pwd);

        //LEN:sLen
        int len = salt.length() + encryptString.length();
        String sLen = "0000"+Integer.toString(len, 36);
        sLen = sLen.substring(sLen.length() - SHORT_DATA_FLAG_LEN);

        // CHECKCODE:checkCode
        String checkCode = DigestUtils.sha1Hex( oriString ).substring(0, CHECK_CODE_LEN);

        sb.append(sLen);
        sb.append(salt);
        sb.append(encryptString);
        sb.append(checkCode);
        return sb.toString().toUpperCase();
    }
    private static Map parseAC(String str){
        try {
            String s = str.toLowerCase();
            Map ret = new HashMap();
            ret.put(MAP_KEY_STR,s);
            int i = 0;
            String ver = s.substring(i, VER_AC.length());
            if(ver == null || !VER_AC.equalsIgnoreCase(ver)){
                return null;
            }

            ret.put(MAP_KEY_VER, ver);

            i += VER_AC.length();
            String sLen = s.substring(i,i+ SHORT_DATA_FLAG_LEN);
            sLen = sLen.replaceFirst("^0+","");
            int nLen = Integer.parseInt(sLen,36);
            ret.put(MAP_KEY_ENCRYPT_STR_LEN, nLen);

            i += SHORT_DATA_FLAG_LEN;
            String salt = s.substring(i, i + SALT_LEN);
            if(salt == null){
                return null;
            }
            ret.put(MAP_KEY_SALT, salt);

            i += SALT_LEN;
            String encryptString = s.substring(i, i + nLen - SALT_LEN );
            if(encryptString == null){
                return null;
            }
            i += nLen - SALT_LEN;
            if((i+1) >= s.length()){
                return null;
            }
            String pwd = DigestUtils.sha1Hex(salt);
            String oriString = AesUtils.decryptString(encryptString, pwd);
            if(oriString == null ){
                return null;
            }
            ret.put(MAP_KEY_ORI, oriString);
            String checkCode = s.substring(i);
            ret.put(MAP_KEY_CHECK_CODE, checkCode);
            String checkCode2 = DigestUtils.sha1Hex(oriString).substring(0,checkCode.length());
            if( checkCode.equalsIgnoreCase( checkCode2)){
                String timeStr = oriString.substring(0,SHORT_TIME_LEN);
                timeStr = timeStr.replaceFirst("^0+","");
                Long t = Long.parseLong(timeStr, MAX_RADIX);
                String c = oriString.substring(SHORT_TIME_LEN);
                ret.put(MAP_KEY_TIME,t);
                ret.put(MAP_KEY_CONTENT,c);
                return  ret;
            }
            return null;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    public static String encryptBA(long timestamp, String text){
        /**
         * prefix|flag|timestamp|flag|text|salt|checkcode 4bits
         */
        int DEF_RADIX = 64;

        StringBuffer sb = new StringBuffer();
        sb.append(VER_BA);

        String salt = DigestUtils.sha1Hex(UUID.randomUUID().toString()).substring(0, SALT_LEN);
        String pwd = DigestUtils.sha1Hex(salt+timestamp);
        //加入text
        String encryptString = AesUtils.encryptString(text, pwd);
        //加密串长度标志的长度
        String segmentLen = convertNumberToString(encryptString.length(), DEF_RADIX);
        //只有一位
        String segmentFlagLen = convertNumberToString(segmentLen.length(), DEF_RADIX);
        sb.append(segmentFlagLen);
        sb.append(segmentLen);
        sb.append(encryptString);

        // 加入timestamp
        String segment = convertNumberToString(timestamp, DEF_RADIX);
        //最多1位
        segmentLen = convertNumberToString(segment.length(), DEF_RADIX);;
        sb.append(segmentLen);
        sb.append(segment);

        //加入 salt
        sb.append(salt);

        String checkCode = DigestUtils.sha1Hex( sb.toString() ).substring(0, CHECK_CODE_LEN);
        sb.append(checkCode);
        return sb.toString();
    }
    /**
     * prefix|uid length|uid|device id length|device id|create time length|create time|expire time length|expire time|rnd|checkcode 4bits
     */
    public static Map parseBA(String key){
        int DEF_RADIX = 64;
        if (key.startsWith(VER_BA)){
            String checkCode = key.substring(key.length() - CHECK_CODE_LEN);
            String checkString = key.substring(0, key.length() - CHECK_CODE_LEN);
            String checkCode2 = DigestUtils.sha1Hex( checkString ).substring(0, CHECK_CODE_LEN);
            if (!checkCode.equalsIgnoreCase(checkCode2)){
                return null;
            }
            Map ret = new HashMap();
            ret.put(MAP_KEY_ORI, key);
            ret.put(MAP_KEY_CHECK_CODE, checkCode);
            String salt = checkString.substring(checkString.length() - SALT_LEN);
            ret.put(MAP_KEY_SALT, salt);

            int pos = VER_BA.length();
            //读取加密串长度
            String segmentFlagLenStr = key.substring(pos, pos+ 1);
            int segmentFlagLen = convertStringToNumber(segmentFlagLenStr, DEF_RADIX).intValue();

            pos += 1;
            String segmentFlag = key.substring(pos, pos+ segmentFlagLen);
            int segmentLen = convertStringToNumber(segmentFlag, DEF_RADIX).intValue();
            //读取加密串
            pos += segmentFlagLen;
            String encryptString = key.substring(pos, pos+ segmentLen);
            pos += segmentLen;

            //读取时间戳长度，1位
            segmentFlag = key.substring(pos, pos+ 1);
            segmentLen = convertStringToNumber(segmentFlag, DEF_RADIX).intValue();
            pos += segmentFlag.length();
            //读取时间戳
            String timetampStr = key.substring(pos, pos+ segmentLen);
            Long timestamp = convertStringToNumber(timetampStr, DEF_RADIX);
            if (timestamp == null){
                return null;
            }
            ret.put(MAP_KEY_TIME, timestamp);

            String pwd = DigestUtils.sha1Hex(salt+timestamp);
            String c = AesUtils.decryptString(encryptString, pwd);
            if (c == null){
                return null;
            }
            ret.put(MAP_KEY_CONTENT, c);

            return ret;
        }
        return null;
    }
    /**
     *
     * @param c 需要加密的字符串
     * @param ver 加密版本，有两种VER_AA,VER_AB
     * @return 返回加密后的字符串
     */
    public static String encrypt(String c, String ver){
        switch (ver){
            case VER_AA:{
                return encryptAA(c);
            }
            case VER_AC:{
                return encryptAC(c);
            }
            default:{
                return null;
            }
        }
    }
    public static String encrypt(String c){
        return encrypt(c, VER_AC);
    }

    /**
     *
     * @param s 解析加密串
     * @return 返回对串的解密的结构，map对象，各个字段含义：
     * ver：加密版本
     * content：解密后的字符串
     * time：时间戳
     * salt：随机盐
     */
    public static Map parse(String s){
        String ver = s.substring(0,2).toUpperCase();
        Map ret = null;
        switch (ver){
            case VER_AA:{
                ret = parseAA(s);
                break;
            }
            case VER_AC:{
                ret = parseAC(s);
                break;
            }
            default:{
                ret = null;
                break;
            }
        }
        return ret;
    }

    /**
     *
     * @param s 需要解密的串
     * @return 返回解密的结果字符串。
     */
    public static String decrypt(String s){
        Map ret = parse(s);
        if (ret == null || !ret.containsKey(MAP_KEY_CONTENT)){
            return null;
        }
        String c = (String)ret.get(MAP_KEY_CONTENT);
        return c;
    }
    public static String decrypt(String s, String ver){
        Map ret = parse(s);
        if (ret == null || !ret.containsKey(MAP_KEY_CONTENT)){
            return null;
        }
        String ver2 = (String)ret.get(MAP_KEY_VER);
        if (ver != null && !ver.equalsIgnoreCase(ver2)){
            return null;
        }
        String c = (String)ret.get(MAP_KEY_CONTENT);
        return c;
    }
    private static void testAC(){
        long t = System.currentTimeMillis()/1000;
        t = 5689818204L;
        String ret = Long.toString(t, MAX_RADIX );
        System.out.println("t:"+t+"|radix ret:"+ret);
        System.out.println("==============AC\n");
        String s  = "123456";
        ret = encryptAC(s);
        Map map = parse(ret);
        System.out.println("s:"+s+"\nencrypt ac ret:"+ret+"\nc:"+map.get(MAP_KEY_CONTENT)+"\nlen:"+ ret.length());

    }
    private static void  testAA(){

        String s  = "123456";
        System.out.println("==============AA\n");
        String ret = encryptAA(s);
        Map map = parse(ret);
        System.out.println("s:"+s+"\nencrypt AA ret:"+ret+"\nc:"+map.get(MAP_KEY_CONTENT)+"\nlen:"+ ret.length());

    }
    private static void  testBA(){
        String ori = "python";
        Long timestamp = System.currentTimeMillis()+19;
        System.out.println("ori:"+ori+", timestamp:"+timestamp);
        String key = encryptBA(timestamp, ori);
        System.out.println("BA encryptString:"+ key);

        Map ret = parseBA(key);
        System.out.println("BA decrypt:");
        System.out.print(ret);
    }
    public static void main(String[] args){
//        Long n = 123456789012345L;
//        System.out.println("ret:"+n+"=>"+(123456789012345L>>>6));
//        System.out.println("ret:"+n+"=>"+Radix64.toString(n));
//        System.out.println("12345.sha1="+DigestUtils.sha1Hex("12345"));
//        System.out.println("12345.AES.encrypt="+AesUtils.encryptString("12345", "12345"));
        testBA();
//        System.out.println(parseBA("BAHrd6a1affe607c42b7e25bad7a5008dae81w9xFLAu897c688fdb92aa2c"));
    }
}
